<template>
    <MyArticle class="readingNotes-webgl-03" title="WebGL学习笔记之三：绘制和变换三角形" date="2021年02月12日">
        <div class="_article-content">
            <p>进一步研究变换矩阵，并在此基础上制作一些简单的效果。</p>
            <h3 class="title">平移再旋转</h3>
            <p>为了简化编程，大多数WebGL开发者都使用矩阵操作函数库来隐藏矩阵计算的细节，简化与矩阵有关的操作。cuon-marix.js是一个专门的函数库，重写旋转变换的代码：</p>
            <pre><code>const xformMatrix = new CuonMatrix4() // CuonMatrix4对象
xformMatrix.setRotate(angle, 0, 0, 1) // 设置为旋转矩阵
const uxformMatrix = gl.getUniformLocation(gl.program, 'u_xformMatrix')
gl.uniformMatrix4fv(uxformMatrix, false, xformMatrix.elements) // 将旋转矩阵传输给顶点着色器</code></pre>
            <canvas ref="canvasRef0" v-show="!img0" class="webgl-canvas spec-canvas"></canvas>
            <img :src="img0" v-show="img0" class="webgl-canvas spec-canvas"/>
            <p>CuonMatrix4类的方法与属性：</p>
            <pre><code>CuonMatrix4.setIdentity() // 将实例初始化为单位矩阵
CuonMatrix4.setRotate(angle, x, y, z) // angle：旋转角度   x, y, z 构成旋转轴
CuonMatrix4.setTranslate(x, y, z) // 将实例设置为平移变换矩阵
CuonMatrix4.setScale(x, y, z) // 将实例设置为缩放矩阵
CuonMatrix4.translate(x, y, z) // 将实例乘以一个平移变换矩阵
CuonMatrix4.rotate(angle, x, y, z) // 将实例乘以一个旋转矩阵
CuonMatrix4.scale(x, y, z) // 将实例乘以一个缩放矩阵
CuonMatrix4.set(m) // 将实例设置为m，m也是一个CuonMatrix4实例
CuonMatrix4.elements // 获取矩阵数据类型化数组</code></pre>
            <p>先平移再旋转复合变换的实现：</p>
            <pre><code>先平移再旋转后的坐标 = 旋转矩阵 * (平移矩阵 * 原始坐标)  ==>  先平移再旋转后的坐标 = (旋转矩阵 * 平移矩阵) * 原始坐标</code></pre>
            <canvas ref="canvasRef1" v-show="!img1" class="webgl-canvas spec-canvas"></canvas>
            <img :src="img1" v-show="img1" class="webgl-canvas spec-canvas"/>
            <pre><code>const xformMatrix = new CuonMatrix4()
xformMatrix.setRotate(20, 0, 0, 1) // 旋转角
xformMatrix.translate(0.5, 0, 0.0) // tx平移0.5

const uxformMatrix = gl.getUniformLocation(gl.program, 'u_xformMatrix')
gl.uniformMatrix4fv(uxformMatrix, false, xformMatrix.elements)</code></pre>
            <p>先旋转，再平移的效果：（两者的效果不一样）</p>
            <canvas ref="canvasRef2" v-show="!img2" class="webgl-canvas spec-canvas"></canvas>
            <img :src="img2" v-show="img2" class="webgl-canvas spec-canvas"/>
            <h3 class="title">动画</h3>
            <p>矩阵运算结合requestAnimationFrame实现一个三角形旋转的动画：</p>
            <canvas ref="canvasRef3" class="webgl-canvas spec-canvas"></canvas>
            <p>定义动画类：</p>
            <pre><code>export default class Ani {
    constructor (el, vertexDatas, vertexCount, xformMatrix, updateCallback) {
        const styles = window.getComputedStyle(el, null)
        el.width = parseInt(styles.width)
        el.height = parseInt(styles.height)
        const gl = el.getContext('webgl2')
        this.gl = gl
        this.xformMatrix = xformMatrix
        this.vertexDatas = vertexDatas
        this.vertexCount = vertexCount
        this.updateCallback = updateCallback // 回调更新矩阵数据的函数
        this.uxformMatrix = null // 矩阵变量的uniform地址
        this.tick = null // requestAnimationFrame的返回

        // 顶点着色器
        const VSHADER_SOURCE = `
            attribute vec4 a_Position;
            uniform mat4 u_xformMatrix;
            void main() {
                gl_Position = u_xformMatrix * a_Position;
                gl_PointSize = 10.0;
            }
        `
        const FSHADER_SOURCE = `
            void main() {
                gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
            }
        `
        if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {
            console.log('init shader error')
            return
        }
        // 设置顶点位置
        const n = this.initVertexBuffers(gl)
        if (n &lt; 0) {
            console.log('initVertexBuffers failed')
            return
        }
        gl.clearColor(0.0, 0.0, 0.0, 1.0)
        gl.clear(gl.COLOR_BUFFER_BIT)
        gl.drawArrays(gl.TRIANGLES, 0, n)
    }

    start () { // 开始动画
        this.stop()
        this.update()
    }

    update () { // 更新
        const that = this
        if (that.updateCallback) {
            that.updateCallback(that.xformMatrix) // 更新变换矩阵
        }
        const gl = that.gl
        gl.uniformMatrix4fv(that.uxformMatrix, false, that.xformMatrix.elements)
        gl.clear(gl.COLOR_BUFFER_BIT)
        gl.drawArrays(gl.TRIANGLES, 0, that.vertexCount)
        that.tick = requestAnimationFrame(that.update.bind(that))
    }

    stop () { // 停止动画
        if (this.tick) {
            cancelAnimationFrame(this.tick)
        }
    }

    initVertexBuffers () {
        const gl = this.gl
        const uxformMatrix = gl.getUniformLocation(gl.program, 'u_xformMatrix')
        this.uxformMatrix = uxformMatrix
        gl.uniformMatrix4fv(uxformMatrix, false, this.xformMatrix.elements)
        const vertices = new Float32Array(this.vertexDatas)
        const n = this.vertexCount
        // 创建缓冲区
        const buffer = gl.createBuffer()
        if (!buffer) {
            return -1
        }
        // 将缓冲区对象绑定到目标
        gl.bindBuffer(gl.ARRAY_BUFFER, buffer)
        // 向缓冲区对象中写入数据
        gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW)
        const aPosition = gl.getAttribLocation(gl.program, 'a_Position')
        // 将缓冲区对象分配给attribute变量
        gl.vertexAttribPointer(aPosition, 2, gl.FLOAT, false, 0, 0)
        // 连接a_Position变量与分配给它的缓冲区对象
        gl.enableVertexAttribArray(aPosition)
        return n
    }
}</code></pre>
            <p>外部调用如下：</p>
            <pre><code>const xformMatrix = new CuonMatrix4()
const rotateStep = 45.0 // 旋转速度
let currAngle = 0
let currTime = Date.now()
const aniUpdate = (formMatrix) => {
    const now = Date.now()
    currAngle = (currAngle + (rotateStep * (now - currTime)) / 1000) % 360
    currTime = now
    formMatrix.setRotate(currAngle, 0, 0, 1)
    formMatrix.translate(0.35, 0, 0)
}
const ani = new Ani(canvasRef.value, [-0.5, -0.5, 0.5, -0.5, 0, 0.5], 3, xformMatrix, aniUpdate)
ani.start() // 启动动画</code></pre>
        </div>
    </MyArticle>
</template>

<script>
import { onBeforeMount, onMounted, ref } from 'vue'
import draw from './draw'
import Ani from './animate'
import CuonMatrix4 from './cuonMatrix'

export default {
    setup () {
        const canvasRef0 = ref(null)
        const img0 = ref(null)
        const canvasRef1 = ref(null)
        const img1 = ref(null)
        const canvasRef2 = ref(null)
        const img2 = ref(null)
        const canvasRef3 = ref(null)

        let ani3 = null
        onMounted(() => {
            const xformMatrix0 = new CuonMatrix4()
            xformMatrix0.setRotate(20, 0, 0, 1)
            img0.value = draw(canvasRef0.value, [-0.5, -0.5, 0.5, -0.5, 0, 0.5], 3, xformMatrix0)

            const xformMatrix1 = new CuonMatrix4()
            xformMatrix1.setRotate(20, 0, 0, 1)
            xformMatrix1.translate(0.5, 0, 0.0)
            img1.value = draw(canvasRef1.value, [-0.5, -0.5, 0.5, -0.5, 0, 0.5], 3, xformMatrix1)

            const xformMatrix2 = new CuonMatrix4()
            xformMatrix2.setTranslate(0.5, 0, 0.0)
            xformMatrix2.rotate(20, 0, 0, 1)
            img2.value = draw(canvasRef2.value, [-0.5, -0.5, 0.5, -0.5, 0, 0.5], 3, xformMatrix2)

            // 动画
            const xformMatrix3 = new CuonMatrix4()
            const rotateStep = 45.0
            let currAngle = 0
            let currTime = Date.now()
            const aniUpdate = (xformMatrix) => {
                const now = Date.now()
                currAngle = (currAngle + (rotateStep * (now - currTime)) / 1000) % 360
                currTime = now
                xformMatrix.setRotate(currAngle, 0, 0, 1)
                xformMatrix.translate(0.35, 0, 0)
            }
            ani3 = new Ani(canvasRef3.value, [-0.5, -0.5, 0.5, -0.5, 0, 0.5], 3, xformMatrix3, aniUpdate)
            ani3.start()
        })

        onBeforeMount(() => {
            if (ani3) {
                ani3.stop()
            }
        })
        return { canvasRef0, img0, canvasRef1, img1, canvasRef2, img2, canvasRef3 }
    }
}
</script>

<style lang="scss">
    @use './style.scss';
</style>
